;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;metrognome.lisp
;;
;;  copyright © 1987-89 Apple Computer
;;
;;
;;  this file creates a simple dialog-based metronome.
;;
;;  the metronome isn't very accurate, but it's cute.
;;  It also shows how to use scroll-bar-dialog-items, and eval-enqueue
;;

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;;  This file uses scroll-bars, so we need to require the file
;;  defines scroll-bar-dialog-items
;;

(require 'scroll-bars (or (probe-file "examples;scroll-bars.lisp")
                          (probe-file "pearl;examples:scroll-bars.lisp")
                          (progn (message-dialog "Cannot find the file Scroll-bars.lisp, which is needed by this file")
                                 (abort))))


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;;  The class of metronome dialogs
;;

(defobject *metrognome* *dialog*)

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;;  There exist procedure initializes a metronome dialog.
;;

(defobfun (exist *metrognome*) (init-list)
  (usual-exist (init-list-default init-list
                                  :window-type :document
                                  :window-size #@(263 60)
                                  :window-title "MetroGnome"
                                  :default-button nil))
  (have 'stop-signal nil)         ;when this is t, the metronome will stop ticking
  (add-dialog-items
   ;a text dialog-item as a title to another text item
   (oneof *static-text-dialog-item*
          :dialog-item-position #@(5 6)
          :dialog-item-text "Click Rate:")
   ;a text dialog-item to show the click rate
   ;we bind an instance variable to this item, so that we can refer to the item later
   (have 'count-display
         (oneof *static-text-dialog-item*
                :dialog-item-position #@(80 6)
                :dialog-item-text "100"))
   ;the button used to start the ticking
   ;the dialog-item-action creates a closure which closes over the metrognome
   ;object.  It then eval-enqueue's a funcall to this closure
   ;we have to create a closure, because when the eval-enqueue'd form is eval'd, there
   ;will be a different current object (i.e. the metronome object won't be the current
   ;object anymore).
   ;the closure sets up a loop to beep until the instance variable STOP-SIGNAL is true
   (oneof *button-dialog-item*
          :dialog-item-text "start"
          :dialog-item-size #@(65 18)
          :dialog-item-position #@(115 6)
          :dialog-item-action
          #'(lambda ()
              (ask my-dialog
                (setq stop-signal nil)
                (let ((current-metrognome (self)))
                  (eval-enqueue
                   `(funcall
                        ',#'(lambda ()
                              (ask current-metrognome
                                (loop
                                  (when stop-signal
                                    (return))
                                  (sleep (- (/ 60 (ask my-scroll-bar
                                                    (scroll-bar-value)))
                                            .2))  ;the duration of one tick
                                  (_sysbeep :word 1))))))))))
   ;the button used to stop the ticking
   ;it just sets the instance variable STOP-SIGNAL to true
   ;this instance variable acts as a flag, and is seen by the loop which does the
   ;ticking.
   (oneof *button-dialog-item*
          :dialog-item-text "stop"
          :dialog-item-size #@(65 18)
          :dialog-item-position #@(190 6)
          :dialog-item-action
          #'(lambda ()
              (ask my-dialog
                (setq stop-signal t))))
   ;the scroll-bar used to set the tick rate
   (have 'my-scroll-bar
         (oneof *scroll-bar-dialog-item*
                :dialog-item-position #@(5 35)
                :direction :horizontal
                :scroll-bar-length 250
                :min-setting 40
                :max-setting 208
                :setting 100
                :dialog-item-action
                #'(lambda (&aux (new-rate (scroll-bar-value)))
                    (ask my-dialog
                      (ask count-display
                        (set-dialog-item-text
                         (format nil "~a" new-rate)))))))))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;;  window-close is has to stop the ticking, otherwise closing the window
;;  will cause an error.
;;

(defobfun (window-close *metrognome*) ()
  (setq stop-signal t)
  (usual-window-close))
 

(setq *metro* (oneof *metrognome*))